package com.atguigu.gmall.index.aspect;


import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.index.annotation.GmallCache;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Component
@Aspect //声明是个切面类
public class GmallCacheAspect {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 1.返回值object
     * 2.参数proceedingJoinPoint
     * 3.抛出异常Throwable
     * 4.proceedingJoinPoint.proceed(args)执行业务方法
     */
    @Around("@annotation(com.atguigu.gmall.index.annotation.GmallCache)")
    public Object cacheAroundAdvice(ProceedingJoinPoint point) throws Throwable {

        Object result=null;

        //获取连接点签名
        MethodSignature signature = (MethodSignature) point.getSignature();//获取连接点签名

        //获取目标方法
        Method method = signature.getMethod();

        // 获取目标方法的GmallCache注解信息
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);
        //获取目标方法的返回值
        Class<?> returnType = method.getReturnType();
        // 获取缓存的前缀
        String prefix = gmallCache.prefix();
        //获取目标方法的参数列表
        Object[] args = point.getArgs();
        //组装成key
        String key = prefix + Arrays.asList(args).toString();


        result = this.cacheHit(returnType, key);
        if(result!=null){
            //缓存中存在，返回
            return result;
        }
        //3没有命中，加锁
        RLock lock = this.redissonClient.getLock("lock" + Arrays.asList(args));
        lock.lock();//加锁

        result=this.cacheHit(returnType,key);
        if(result!=null){
            //缓存中存在，返回
            lock.unlock();
            return result;
        }

        //还没有则执行目标方法
        result= point.proceed(args);
        //放入缓存，释放分布式锁
        int randomTime = gmallCache.randomTime();
        int timeout = gmallCache.timeout();
        this.redisTemplate.opsForValue().set(key,JSON.toJSONString(result),timeout+(int)(Math.random()*randomTime), TimeUnit.MINUTES);
        // 释放锁
        lock.unlock();
        return result;
    }

    /**
     * 抽取查询缓存的方法
     *
     * @param signature
     * @param key
     * @return
     */
    private Object cacheHit(Class<?> returnType, String key) {
        //1、从缓存中查询
        String json = this.redisTemplate.opsForValue().get(key);
        //2、命中直接返回
        if(StringUtils.isNotBlank(json)){

            return JSON.parseObject(json,returnType);//反序列化
        }else{
            return null;
        }
    }
}
